package com.secondriver.url.filter;

import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import com.secondriver.url.filter.match.URLCacheObject;
import com.secondriver.url.filter.match.URLPatternMatch;
import com.secondriver.url.filter.match.groovy.DefalutURLPatternMatch;
import com.secondriver.url.filter.urlrealm.Subject;
import com.secondriver.url.filter.urlrealm.URLBean;
import com.secondriver.url.filter.urlrealm.URLRealm;
import com.secondriver.url.filter.urlrealm.groovy.DefaultURLRealm;

public class AccessURLFilter implements Filter {

	private String accessSubjectKey = AccessURLFilter.class.getName()
			+ "_access_subject_key";

	private String loginURL;

	private String denyURL;

	private String subjectIdKey;

	private String dbConfig;

	private String urlRealmClass = "com.secondriver.url.filter.urlrealm.groovy.DefaultURLRealm";

	private String urlPatternMatchClass = "com.secondriver.url.filter.match.groovy.DefaultURLPatternMatch";

	private URLRealm urlRealm;

	private URLPatternMatch urlPatternMatch;

	private static final ConcurrentHashMap<String, URLCacheObject> SUBJECT_REQUEST_CACHE = new ConcurrentHashMap<String, URLCacheObject>();

	private static final Set<String> OPEN_URL_REQUEST_CACHE = new HashSet<String>();

	private static final Set<String> APP_OPEN_URLS = new HashSet<String>();

	public AccessURLFilter() {
	}

	public void init(FilterConfig fConfig) throws ServletException {
		initParamsCheck(fConfig);
		initURLRealmObject(fConfig);
		initURLPatternMatchObject(fConfig);
		initOpenUrlData();
		System.out.println(Calendar.getInstance(TimeZone.getDefault())
				.getTime()
				+ ":"
				+ this.getClass().getName()
				+ " init successed.");
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;

		String contextPath = req.getContextPath();
		String requestURI = req.getRequestURI();
		String requestPath = requestURI.substring(contextPath.length());

		if (this.isOpen(requestPath)) {
			chain.doFilter(request, response);
		} else {
			HttpSession session = req.getSession();
			if (this.isAuthentication(session)) {
				String subjectid = (String) session
						.getAttribute(this.subjectIdKey);
				Subject subject = this.getSubject(session, subjectid);
				if (this.isAuthorization(requestPath, subject)) {
					chain.doFilter(request, response);
				} else {
					req.getRequestDispatcher(this.denyURL).forward(request,
							response);
				}
			} else {
				req.getRequestDispatcher(this.loginURL).forward(request,
						response);
			}
		}
	}

	public void destroy() {
		APP_OPEN_URLS.clear();
	}

	/**
	 * 初始化参数检查
	 * 
	 * @param fConfig
	 */
	private void initParamsCheck(FilterConfig fConfig) {
		String loginURL = fConfig.getInitParameter("loginURL");
		String denyURL = fConfig.getInitParameter("denyURL");
		String subjectIdKey = fConfig.getInitParameter("subjectIdKey");
		String dbConfig = fConfig.getInitParameter("dbConfig");

		if (null == denyURL || null == loginURL || null == subjectIdKey
				|| null == dbConfig) {
			throw new IllegalArgumentException(
					AccessURLFilter.class.getName()
							+ " init-param loading error, please check the param [denyURL, loginURL, dbConfig, subjectIdKey] in web.xml,"
							+ " there are must configuration.");
		} else {
			this.denyURL = denyURL;
			this.loginURL = loginURL;
			this.subjectIdKey = subjectIdKey;
			this.dbConfig = dbConfig;
		}

		String accessKey = fConfig.getInitParameter(accessSubjectKey);
		if (null != accessKey && accessKey.trim().length() != 0) {
			this.accessSubjectKey = accessKey;
		}
	}

	/**
	 * 初始化URL访问控制数据源域对象
	 * 
	 * @param fConfig
	 */
	private void initURLRealmObject(FilterConfig fConfig) {
		String urlRealmClass = fConfig.getInitParameter("urlRealmClass");
		if (null == urlRealmClass) {
			this.urlRealm = new DefaultURLRealm(new String(this.dbConfig));
		} else {
			this.urlRealmClass = urlRealmClass;
			try {
				this.urlRealm = (URLRealm) Class.forName(this.urlRealmClass)
						.newInstance();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 初始化URL与请求模式匹配对象
	 * 
	 * @param fConfig
	 */
	private void initURLPatternMatchObject(FilterConfig fConfig) {
		String urlPatternMatchClass = fConfig
				.getInitParameter("urlPatternMatchClass");
		if (null == urlPatternMatchClass) {
			this.urlPatternMatch = new DefalutURLPatternMatch();
		} else {
			this.urlPatternMatchClass = urlPatternMatchClass;
			try {
				this.urlPatternMatch = (URLPatternMatch) Class.forName(
						this.urlPatternMatchClass).newInstance();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}

	private void initOpenUrlData() {
		APP_OPEN_URLS.addAll(this.getOpenUrls());
	}

	/**
	 * 判断当前请求是否是开放模式
	 * 
	 * @param contextPath
	 * @return
	 */
	private boolean isOpen(String contextPath) {
		if (OPEN_URL_REQUEST_CACHE.contains(contextPath)) {
			return true;
		} else {
			boolean result = urlPatternMatch.isPass(contextPath, APP_OPEN_URLS);
			if (result) {
				OPEN_URL_REQUEST_CACHE.add(contextPath);
			}
			return result;
		}
	}

	/**
	 * 判断当前用户是否认证
	 * 
	 * @param session
	 * @return
	 */
	private boolean isAuthentication(HttpSession session) {
		return null != (String) session.getAttribute(subjectIdKey);
	}

	/**
	 * 判断用户是否被授权访问当前请求
	 * 
	 * @param session
	 * @return
	 */
	private boolean isAuthorization(String contextPath, Subject subject) {
		URLCacheObject urlCacheObject = SUBJECT_REQUEST_CACHE.get(subject
				.getObjectKey());
		if (null == urlCacheObject) {
			urlCacheObject = new URLCacheObject();
			SUBJECT_REQUEST_CACHE.put(subject.getObjectKey(), urlCacheObject);
		}
		if (urlCacheObject.isExist(contextPath)) {
			return true;
		} else {
			boolean result = urlPatternMatch.isPass(contextPath,
					subject.getUrls());
			if (result) {
				urlCacheObject.add(contextPath);
			}
			return result;
		}
	}

	/**
	 * 获取Subject
	 * 
	 * @param session
	 * @param subjectid
	 * @return
	 */
	private Subject getSubject(HttpSession session, String subjectid) {
		Subject subject = (Subject) session.getAttribute(this.accessSubjectKey);
		String sessionid = session.getId();
		if (null != subject && subject.getSessionid().equals(sessionid)
				&& subject.getSubjectid().equals(subjectid)) {
			;
		} else {
			Map<String, String> param = new HashMap<String, String>();
			param.put("sessionid", session.getId());
			param.put("subjectid", subjectid);
			subject = this.urlRealm.buildSubject(param);
		}
		return subject;
	}

	/**
	 * 获取开放的URL
	 * 
	 * @return
	 */
	private Set<String> getOpenUrls() {
		List<URLBean> listURLBeans = this.urlRealm.buildOpenURLBean();
		Set<String> urls = new HashSet<String>();
		for (URLBean urlbean : listURLBeans) {
			urls.add(String.valueOf(urlbean.getUrl()));
		}
		return urls;
	}
}
